#include "znx_string.h"

static u_char *znx_sprintf_num(u_char *buf,
    u_char *last, uint64_t ui64, u_char zero,
    uint64_t hexadecimal, uint64_t width);


void
znx_strlow(u_char *dst, u_char *src, size_t n)
{
    while (n) {
        *dst = znx_tolower(*src);
        dst++;
        src++;
        n--;
    }
}


void
znx_sort(void *base, size_t n, size_t size,
    int (*cmp)(const void *, const void *))
{
    u_char      *p1, *p2, *p;

    p = malloc(size);
    if (p == NULL) {
        return;
    }

    for (p1 = (u_char *) base + size;
         p1 < (u_char *) base + n * size;
         p1 += size)
    {
        znx_memcpy(p, p1, size);

        for (p2 = p1;
             p2 > (u_char *) base && cmp(p2 - size, p) > 0;
             p2 -= size)
        {
            znx_memcpy(p2, p2 - size, size);
        }

        znx_memcpy(p2, p, size);
    }

    free(p);
}


int
znx_str_cmp(const znx_str_t *str1, const znx_str_t *str2)
{
    size_t n = str1->len < str2->len ? str1->len : str2->len;

    u_char a, b;
    for (size_t i = 0; i < n; i++) {
        a = *(str1->data + i);
        b = *(str2->data + i);

        if (a > b) {
            return 1;
        }

        if (a < b) {
            return -1;
        }
    }

    if (str1->len == str2->len) {
        return 0;
    }

    return str1->len > str2->len ? 1 : -1;
}


int
znx_str_cmp_ext(const znx_str_t *str1, const char *str2)
{
    u_char          a, b;
    size_t          i = 0;

    while (i < str1->len && *str2 != '\0') {
        a = *(str1->data + i);
        b = (u_char)(*str2);

        if (a > b) {
            return 1;
        }

        if (a < b) {
            return -1;
        }

        i++;
        str2++;
    }

    if (*str2 == '\0' && i == str1->len) {
        return 0;
    }

    return (*str2 == '\0') ? 1 : -1;
}


int
znx_str_case_cmp(const znx_str_t *str1, const znx_str_t *str2)
{
    size_t n = str1->len < str2->len ? str1->len : str2->len;

    u_char a, b;
    for (size_t i = 0; i < n; i++) {
        a = znx_tolower(*(str1->data + i));
        b = znx_tolower(*(str2->data + i));

        if (a > b) {
            return 1;
        }

        if (a < b) {
            return -1;
        }
    }

    if (str1->len == str2->len) {
        return 0;
    }

    return str1->len > str2->len ? 1 : -1;
}


int
znx_str_case_cmp_ext(const znx_str_t *str1, const char *str2)
{
    u_char          a, b;
    size_t          i = 0;

    while (i < str1->len && *str2 != '\0') {
        a = znx_tolower(*(str1->data + i));
        b = znx_tolower(*str2);

        if (a > b) {
            return 1;
        }

        if (a < b) {
            return -1;
        }

        i++;
        str2++;
    }

    if (*str2 == '\0' && i == str1->len) {
        return 0;
    }

    return (*str2 == '\0') ? 1 : -1;
}


znx_bool_t
znx_str_to_cstr(const znx_str_t *str, u_char *buf, size_t buf_size)
{
    if (buf_size < str->len + 1) {
        return ZNX_FALSE;
    }

    size_t len = str->len;
    znx_memcpy(buf, str->data, len);
    buf[len] = '\0';
    return ZNX_TRUE;
}


znx_bool_t
znx_str_to_int64(znx_str_t *str, int64_t *res)
{
    if (str->len == 0) {
        *res = 0;
        return ZNX_TRUE;
    }

    u_char  ch;
    int64_t r       = 0;
    int64_t flag    = 1;
    u_char  *p      = str->data;
    u_char  *end    = str->data + str->len;

    if (*p == '-') {
        flag = -1;
        p++;
    } else if (*p == '+') {
        p++;
    }

    for (; p != end; p++) {
        ch = *p;
        if (ch < '0' || ch > '9') {
            return ZNX_FALSE;
        }

        r = r * 10 + (ch - '0');
    }

    *res = r * flag;
    return ZNX_TRUE;
}


znx_bool_t
znx_str_to_double(znx_str_t *str, double *res)
{
    if (str->len == 0) {
        *res = 0.0;
        return ZNX_TRUE;
    }

    u_char  ch;
    double  r        = 0;
    double  flag     = 1.0;
    double  e        = 0;
    double  eflag    = 1;
    double  deviced  = 1;
    u_char  *p       = str->data;
    u_char  *end     = str->data + str->len;

    if (*p == '-') {
        flag = -1;
        p++;
    } else if (*p == '+') {
        p++;
    }

    for (; p != end; p++) {
        ch = *p;
        if (ch < '0' || ch > '9') {
            break;
        }

        r = r * 10 + (ch - '0');
    }

    if (p != end && *p == '.') {
        p++;
        for (; p != end; p++) {
            ch = *p;
            if (ch < '0' || ch > '9') {
                break;
            }

            r = r * 10 + (ch - '0');
            deviced *= 10;
        }
    }

    if (p != end && *p == 'e') {
        p++;
        switch(*p) {
        case '+':
            p++;
            break;
        case '-':
            eflag = -1;
            p++;
            break;
        default:
            break;
        }

        for (; p != end; p++) {
            ch = *p;
            if (ch < '0' || ch > '9') {
                break;
            }

            e = e * 10 + (ch - '0');
        }
    }

    if (p != end) {
        return ZNX_FALSE;
    }

    if (eflag > 0) {
        *res = (flag * r) / deviced * pow10(e);
    } else {
        *res = (flag * r) / deviced / pow10(e);
    }

    return ZNX_TRUE;
}


znx_bool_t
znx_str_to_bool(znx_str_t *str, znx_bool_t *b)
{
    if (str->len == sizeof("true") - 1 &&
        znx_strncasecmp(str->data, "true", str->len) == 0)
    {
        *b = ZNX_TRUE;
        return ZNX_TRUE;
    }

    if (str->len == sizeof("false") - 1 &&
        znx_strncasecmp(str->data, "false", str->len) == 0)
    {
        *b = ZNX_FALSE;
        return ZNX_TRUE;
    }

    return ZNX_FALSE;
}


const char *
znx_path_base(const char *path)
{
    const char  *last  = path;
    const char  *p     = path;

    while (*p != '\0') {
        if (*p == '/') {
            last = p + 1;
        }

        p++;
    }

    return last;
}


znx_bool_t
znx_parse_size(const u_char *str, int64_t *res)
{
    const u_char    *p = str;
    u_char          ch;

    while (*p != '\0') {
        if (*p < '0' || *p > '9') {
            break;
        }
        p++;
    }

    znx_str_t tmp = {
        .len = (size_t)(p - str),
        .data = (u_char *)str,
    };

    if (!znx_str_to_int64(&tmp, res)) {
        return ZNX_FALSE;
    }

    if (*p == '\0') {
        return ZNX_TRUE;
    }

    ch = znx_tolower(*p);

    if (ch == 'k') {
        *res = *res * 1024;
        return ZNX_TRUE;
    }

    if (ch == 'm') {
        *res = *res * 1024 * 1024;
        return ZNX_TRUE;
    }

    if (ch == 'g') {
        *res = *res * 1024 * 1024 * 1024;
        return ZNX_TRUE;
    }

    if (ch == 't') {
        *res = *res * 1024 * 1024 * 1024 * 1024;
        return ZNX_TRUE;
    }

    return ZNX_FALSE;
}


u_char *
znx_cpystrn(u_char *dst, u_char *src, size_t n)
{
    if (n == 0) {
        return dst;
    }

    while (--n) {
        *dst = *src;

        if (*dst == '\0') {
            return dst;
        }

        dst++;
        src++;
    }

    *dst = '\0';

    return dst;
}


void
znx_strrev(u_char *buf, size_t n)
{
    u_char      c;
    size_t      i;

    if (buf == NULL || n == 1) {
        return ;
    }

    for (i = 0; i < n / 2; i++) {
        c = buf[i];
        buf[i] = buf[n-1-i];
        buf[n-1-i] = c;
    }
}


/*
 * supported formats:
 *    %[0][width][x][X]O        off_t
 *    %[0][width]T              time_t
 *    %[0][width][u][x|X]z      ssize_t/size_t
 *    %[0][width][u][x|X]d      int/u_int
 *    %[0][width][u][x|X]l      long
 *    %[0][width|m][u][x|X]i    int64/uint64/int/u_int
 *    %[0][width][u][x|X]D      int32_t/uint32_t
 *    %[0][width][u][x|X]L      int64_t/uint64_t
 *    %[0][width][.width]f      double, max valid number fits to %18.15f
 *    %p                        void *
 *    %V                        znx_str_t *
 *    %v                        znx_str_t *
 *    %s                        null-terminated string
 *    %*s                       length and string
 *    %Z                        '\0'
 *    %N                        '\n'
 *    %c                        char
 *    %%                        %
 *
 *  reserved:
 *    %t                        ptrdiff_t
 *    %S                        null-terminated wchar string
 *    %C                        wchar
 */
u_char *
znx_sprintf(u_char *buf, const char *fmt, ...)
{
    u_char      *p;
    va_list     args;

    va_start(args, fmt);
    p = znx_vslprintf(buf, (void *) -1, fmt, args);
    va_end(args);

    return p;
}


u_char *
znx_snprintf(u_char *buf, size_t max, const char *fmt, ...)
{
    u_char    *p;
    va_list   args;

    va_start(args, fmt);
    p = znx_vslprintf(buf, buf + max, fmt, args);
    va_end(args);

    return p;
}


u_char *
znx_slprintf(u_char *buf, u_char *last, const char *fmt, ...)
{
    u_char      *p;
    va_list     args;

    va_start(args, fmt);
    p = znx_vslprintf(buf, last, fmt, args);
    va_end(args);

    return p;
}


u_char *
znx_vslprintf(u_char *buf, u_char *last, const char *fmt, va_list args)
{
    u_char                  *p, zero;
    int                     d;
    double                  f;
    size_t                  len, slen;
    int64_t                 i64;
    uint64_t                ui64, frac;
    uint64_t                width, sign, hex, max_width, frac_width, scale, n;
    znx_str_t               *v;

    /* we will append \0 at the end */
    last = last - 1;

    while (*fmt && buf < last - 1) {

        /*
         * "buf < last" means that we could copy at least one character:
         * the plain character, "%%", "%c", and minus without the checking
         */

        if (*fmt == '%') {

            i64 = 0;
            ui64 = 0;

            zero = (u_char) ((*++fmt == '0') ? '0' : ' ');
            width = 0;
            sign = 1;
            hex = 0;
            max_width = 0;
            frac_width = 0;
            slen = (size_t) -1;

            while (*fmt >= '0' && *fmt <= '9') {
                width = width * 10 + (uint64_t)(*fmt++ - '0');
            }


            for ( ;; ) {
                switch (*fmt) {

                case 'u':
                    sign = 0;
                    fmt++;
                    continue;

                case 'm':
                    max_width = 1;
                    fmt++;
                    continue;

                case 'X':
                    hex = 2;
                    sign = 0;
                    fmt++;
                    continue;

                case 'x':
                    hex = 1;
                    sign = 0;
                    fmt++;
                    continue;

                case '.':
                    fmt++;

                    while (*fmt >= '0' && *fmt <= '9') {
                        frac_width = frac_width * 10 + (uint64_t)(*fmt++ - '0');
                    }

                    break;

                case '*':
                    slen = va_arg(args, size_t);
                    fmt++;
                    continue;

                default:
                    break;
                }

                break;
            }


            switch (*fmt) {

            case 'V':
                v = va_arg(args, znx_str_t *);

                len = ZNX_MIN(((size_t) (last - buf)), v->len);
                buf = znx_cpymem(buf, v->data, len);
                fmt++;

                continue;

            case 'v':
                v = va_arg(args, znx_str_t *);

                len = ZNX_MIN(((size_t) (last - buf)), v->len);
                buf = znx_cpymem(buf, v->data, len);
                fmt++;

                continue;

            case 's':
                p = va_arg(args, u_char *);

                if (slen == (size_t) -1) {
                    while (*p && buf < last) {
                        *buf++ = *p++;
                    }

                } else {
                    len = ZNX_MIN(((size_t) (last - buf)), slen);
                    buf = znx_cpymem(buf, p, len);
                }

                fmt++;

                continue;

            case 'O':
                i64 = (int64_t) va_arg(args, off_t);
                sign = 1;
                break;

            case 'T':
                i64 = (int64_t) va_arg(args, time_t);
                sign = 1;
                break;

            case 'z':
                if (sign) {
                    i64 = (int64_t) va_arg(args, ssize_t);
                } else {
                    ui64 = (uint64_t) va_arg(args, size_t);
                }
                break;

            case 'i':
                if (sign) {
                    i64 = (int64_t) va_arg(args, int64_t);
                } else {
                    ui64 = (uint64_t) va_arg(args, uint64_t);
                }

                if (max_width) {
                    width = ZNX_INT64_LEN;
                }

                break;

            case 'd':
                if (sign) {
                    i64 = (int64_t) va_arg(args, int);
                } else {
                    ui64 = (uint64_t) va_arg(args, u_int);
                }
                break;

            case 'l':
                if (sign) {
                    i64 = (int64_t) va_arg(args, long);
                } else {
                    ui64 = (uint64_t) va_arg(args, u_long);
                }
                break;

            case 'D':
                if (sign) {
                    i64 = (int64_t) va_arg(args, int32_t);
                } else {
                    ui64 = (uint64_t) va_arg(args, uint32_t);
                }
                break;

            case 'L':
                if (sign) {
                    i64 = va_arg(args, int64_t);
                } else {
                    ui64 = va_arg(args, uint64_t);
                }
                break;

            case 'f':
                f = va_arg(args, double);

                if (f < 0) {
                    *buf++ = '-';
                    f = -f;
                }

                ui64 = (uint64_t) f;
                frac = 0;

                if (frac_width) {

                    scale = 1;
                    for (n = frac_width; n; n--) {
                        scale *= 10;
                    }

                    frac = (uint64_t) ((f - (double) ui64) * (double)(scale) + 0.5);

                    if (frac == scale) {
                        ui64++;
                        frac = 0;
                    }
                }

                buf = znx_sprintf_num(buf, last, ui64, zero, 0, width);

                if (frac_width) {
                    if (buf < last) {
                        *buf++ = '.';
                    }

                    buf = znx_sprintf_num(buf, last, frac, '0', 0, frac_width);
                }

                fmt++;

                continue;

            case 'p':
                ui64 = (uintptr_t) va_arg(args, void *);
                hex = 2;
                sign = 0;
                zero = '0';
                width = 2 * sizeof(void *);
                break;

            case 'c':
                d = va_arg(args, int);
                *buf++ = (u_char) (d & 0xff);
                fmt++;

                continue;

            case 'Z':
                *buf++ = '\0';
                fmt++;

                continue;

            case 'N':
#if (ZNX_WIN32)
                *buf++ = CR;
                if (buf < last) {
                    *buf++ = LF;
                }
#else
                *buf++ = LF;
#endif
                fmt++;

                continue;

            case '%':
                *buf++ = '%';
                fmt++;

                continue;

            default:
                *buf++ = (u_char) *fmt++;

                continue;
            }

            if (sign) {
                if (i64 < 0) {
                    *buf++ = '-';
                    ui64 = (uint64_t) -i64;

                } else {
                    ui64 = (uint64_t) i64;
                }
            }

            buf = znx_sprintf_num(buf, last, ui64, zero, hex, width);

            fmt++;

        } else {
            *buf++ = (u_char) *fmt++;
        }
    }

    *buf = '\0';

    return buf;
}


static u_char *
znx_sprintf_num(u_char *buf, u_char *last, uint64_t ui64, u_char zero,
    uint64_t hexadecimal, uint64_t width)
{
    char            *p, temp[ZNX_INT64_LEN + 1];
                       /*
                        * we need temp[ZNX_INT64_LEN] only,
                        * but icc issues the warning
                        */
    size_t          len;
    uint32_t        ui32;
    static char     hex[] = "0123456789abcdef";
    static char     HEX[] = "0123456789ABCDEF";

    p = temp + ZNX_INT64_LEN;

    if (hexadecimal == 0) {

        if (ui64 <= (uint64_t) ZNX_MAX_UINT32_VALUE) {

            /*
             * To divide 64-bit numbers and to find remainders
             * on the x86 platform gcc and icc call the libc functions
             * [u]divdi3() and [u]moddi3(), they call another function
             * in its turn.  On FreeBSD it is the qdivrem() function,
             * its source code is about 170 lines of the code.
             * The glibc counterpart is about 150 lines of the code.
             *
             * For 32-bit numbers and some divisors gcc and icc use
             * a inlined multiplication and shifts.  For example,
             * unsigned "i32 / 10" is compiled to
             *
             *     (i32 * 0xCCCCCCCD) >> 35
             */

            ui32 = (uint32_t) ui64;

            do {
                *--p = (char) (ui32 % 10 + '0');
            } while (ui32 /= 10);

        } else {
            do {
                *--p = (char) (ui64 % 10 + '0');
            } while (ui64 /= 10);
        }

    } else if (hexadecimal == 1) {

        do {

            /* the "(uint32_t)" cast disables the BCC's warning */
            *--p = hex[(uint32_t) (ui64 & 0xf)];

        } while (ui64 >>= 4);

    } else { /* hexadecimal == 2 */

        do {

            /* the "(uint32_t)" cast disables the BCC's warning */
            *--p = HEX[(uint32_t) (ui64 & 0xf)];

        } while (ui64 >>= 4);
    }

    /* zero or space padding */

    len = (size_t) ((temp + ZNX_INT64_LEN) - p);

    while (len++ < width && buf < last) {
        *buf++ = zero;
    }

    /* number safe copy */

    len = (size_t) ((temp + ZNX_INT64_LEN) - p);

    if (buf + len > last) {
        len = (size_t) (last - buf);
    }

    return znx_cpymem(buf, p, len);
}


u_char *
znx_hex_dump(u_char *dst, u_char *src, size_t len)
{
    static u_char  hex[] = "0123456789abcdef";

    while (len--) {
        *dst++ = hex[*src >> 4];
        *dst++ = hex[*src++ & 0xf];
    }

    return dst;
}
